/*
 * uart.cpp
 *
 * Created: 02/07/2011 09:16:58
 *  Author: guy
 */ 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "uart.h"

/*
 *	Size these buffers according to needs/ram
 *	can be any size from 0x02 - 0xff each
 *	each buffer can be a different size
 *	PIPOlog will send bursts of 31 bytes so buffer must be >31
 */
#define RX_BUFF_SIZE 8
#define TX_BUFF_SIZE 32

/*
 *	declare vars as static to stop them being used outside of this file
 */
static volatile uint8_t rx_buff[RX_BUFF_SIZE];
static volatile uint8_t tx_buff[TX_BUFF_SIZE];
static volatile uint8_t rx_buff_head;
static volatile uint8_t rx_buff_tail;
static volatile uint8_t tx_buff_head;
static volatile uint8_t tx_buff_tail;

/*
 *	Initialize the UART
 *	double rate asynchronous
 *	on exit interrupts will be globally enabled
 */
void uart_init(uint32_t baud)
{
	cli();	// datasheet says interrupts must be globally disabled
	// datasheet gives UBRR calc as =(F_CPU/(8*baud)) - 1
	// this gives an error >2.1% @ 57600 = no good
	// doing it this way gives error of <0.8% @ 57600 which is ok
	UBRR0H = HIGH((F_CPU /4 /baud - 1) /2);
	UBRR0L = LOW((F_CPU /4 /baud - 1) /2);
	UCSR0A = (1<<U2X0); // double rate
	UCSR0B = (1<<RXEN0) | (1<<TXEN0) | (1<<RXCIE0);
	UCSR0C = (11<<UCSZ00); // 8bit
	tx_buff_head = tx_buff_tail = 0;
	rx_buff_head = rx_buff_tail = 0;
	sei();
}

/*
 * Send a string stored in flash
 */
void uart_TxString_P(const char *str)
{
	while (pgm_read_byte(str) != 0x00) 
		uart_TxByte(pgm_read_byte(str++)); 
}

/*
 * Send out a 16 bit number - little endian i.e. lsB first
 */
void uart_TxInt16(int16_t i)
{
	uart_TxByte(LOW(i)); 
	uart_TxByte(HIGH(i)); 
}


/*
 * Put a byte into teh output buffer to transmit when ready
 * if teh buffer is full the routine will block 
 */
void uart_TxByte(uint8_t c)
{
	uint8_t i;

	i = tx_buff_head + 1; // use temp var in case interrupt triggered
	if (i >= TX_BUFF_SIZE) i = 0;
	while (tx_buff_tail == i) ; // wait until there is space

	tx_buff[i] = c;
	tx_buff_head = i;
	UCSR0B |= (1<<UDRIE0); // make sure TX int is enabled
}

/*
 *	get a byte from teh rc buffer
 *	if the buffer is empty we cannot return anything as it could
 *	be interpreted as data - so we must block and wait
 *	call uart_getAvailable() first to make sure there are bytes waiting
 */
uint8_t uart_RxByte(void)
{
    uint8_t c, i;
	// if empty we must block and wait
	while (rx_buff_head == rx_buff_tail) ;
	
    i = rx_buff_tail + 1;
    if (i >= RX_BUFF_SIZE) i = 0;
    c = rx_buff[i];
    rx_buff_tail = i;
    return c;
}

/*
 *	Return the number of bytes waiting in the rx buffer.
 */
uint8_t uart_getAvailable(void)
{
	uint8_t h, t;

	h = rx_buff_head;
	t = rx_buff_tail;
	if (h >= t) {
		return h - t;
	} else {
		return RX_BUFF_SIZE + h - t;
	}
}

/*
 *	when UDR is clear interrupt is triggered
 *	if no bytes remain in tx buffer then disable int
 *	else send next byte out & update buffer tail
 */
ISR(USART_UDRE_vect)
{
	uint8_t i;

	if (tx_buff_head == tx_buff_tail) 	{
		// buffer is empty, no more bytes to send
		// so, disable transmit interrupt
		UCSR0B &= ~(1<<UDRIE0);
	} else {
		i = tx_buff_tail + 1;
		if (i >= TX_BUFF_SIZE) i = 0;
		UDR0 = tx_buff[i];
		tx_buff_tail = i;
	}
}

/*
 *	Receive Interrupt
 *	When byte received in UDR
 *	store byte in buffer if there is space
 *	else discard
 */
ISR(USART_RX_vect)
{
	uint8_t ch, i;

	ch = UDR0;
	i = rx_buff_head + 1;
	if (i >= RX_BUFF_SIZE) i = 0;
	if (i != rx_buff_tail) { // if there is space
		rx_buff[i] = ch;
		rx_buff_head = i;
	} // else byte is discarded
}

